﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;

namespace Axial
{
	public class BreakCondition : CodeSegment
	{
		public string Condition { get; private set; }
		public int EndOffset { get; private set; }

		public BreakCondition(IEnumerable<CodeStatement> statements)
		{
			if (!(statements.First().GetType() == typeof(BreakConditionStatement)))
				throw new ArgumentException("A BreakCondition must start with a BreakStatement");
			var conditions = statements.TakeWhile(s => s.GetType() == typeof(BreakConditionStatement)).Cast<BreakConditionStatement>().ToArray();

			bool useOr = statements.Count() == conditions.Count() || conditions.Count() > 1;
			Condition = ConditionHelper.CombineConditions(conditions, useOr ? conditions.Last().FlowTarget : -1);
			EndOffset = conditions.Last().StartOffset + 1;

			// TODO: test and handle else and nested if
		}

		public override string ToString()
		{
			return string.Format("if ({0})\n{{\nbreak\n}}\n", Condition);
		}

		public override JSWriter ToJSWriter()
		{
			JSWriter writer = new JSWriter();
			writer.AddIfBlock(Condition, new BreakStatement(EndOffset).ToJSWriter(), new JSWriter());
			return writer;
		}
	}

	public class ContinueCondition : CodeSegment
	{
		public string Condition { get; private set; }
		public int EndOffset { get; private set; }

		public ContinueCondition(IEnumerable<CodeStatement> statements)
		{
			if (!(statements.First().GetType() == typeof(ContinueConditionStatement)))
				throw new ArgumentException("A BreakCondition must start with a ContinueConditionStatement");
			var conditions = statements.TakeWhile(s => s.GetType() == typeof(ContinueConditionStatement)).Cast<ContinueConditionStatement>().ToArray();

			bool useOr = true;
			Condition = ConditionHelper.CombineConditions(conditions, useOr ? conditions.Last().FlowTarget : -1);
			EndOffset = conditions.Last().StartOffset + 1;

			// TODO: test and handle else and nested if
		}

		public override string ToString()
		{
			return string.Format("if ({0})\n{{\ncontinue\n}}\n", Condition);
		}

		public override JSWriter ToJSWriter()
		{
			JSWriter writer = new JSWriter();
			writer.AddIfBlock(Condition, new ContinueStatement(EndOffset).ToJSWriter(), new JSWriter());
			return writer;
		}
	}

	public class IfCondition : CodeSegment
	{
		public string Condition { get; private set; }
		public int TrueOffset { get; private set; }
		public int FalseOffset { get; private set; }
		public int EndOffset { get; private set; }

		public CodeSegment TrueSegment { get; private set; }
		public CodeSegment FalseSegment { get; private set; }

		public IfCondition(IEnumerable<CodeStatement> statements)
		{
			if (!(statements.First().GetType() == typeof(ConditionStatement)))
				throw new ArgumentException("An IfCondition must start with a ConditionStatement");
			var conditions = statements.TakeWhile(s => s.GetType() == typeof(ConditionStatement)).Cast<ConditionStatement>();

			// break up the conditions in the case of consecutive conditions
			// TODO: combine previous statement and this block?
			int maxTargetOffset = conditions.Max(c => c.FlowTarget);
			int lastConditionOffset = conditions.Last(c => c.FlowTarget == maxTargetOffset).StartOffset;
			conditions = conditions.TakeWhile(c => c.StartOffset <= lastConditionOffset);

			// find where to jump to on true and false
			TrueOffset = statements.Skip(conditions.Count()).First().StartOffset;
			// if it exists, the second instruction with the highest target is the first true statement
			if (conditions.Count(s => s.FlowTarget != maxTargetOffset) > 0)
				TrueOffset = conditions.Where(s => s.FlowTarget != maxTargetOffset).Max(s => s.FlowTarget);

			FalseOffset = conditions.Last().FlowTarget;
			var trueStatements = statements.Where(s => s.StartOffset >= TrueOffset && s.StartOffset < FalseOffset);

			// find if there's an else
			CodeStatement possibleElse = statements.Last(s => s.StartOffset < FalseOffset);
			EndOffset = FalseOffset;
			if (possibleElse.GetType() == typeof(JumpStatement))
			{
				trueStatements = trueStatements.Take(trueStatements.Count() - 1);
				EndOffset = (possibleElse as JumpStatement).FlowTarget;
			}
			if (possibleElse.IsReturn)
				EndOffset = int.MaxValue;
			var falseStatements = statements.Where(s => s.StartOffset >= FalseOffset && s.StartOffset < EndOffset);

			// in the case of if, if, make sure conditions aren't part of the true statement - ex: if (a) if (b) c();
			conditions = conditions.Where(c => c.StartOffset < TrueOffset);

			TrueSegment = new CodeSegment(trueStatements);
			if (TrueSegment.Statements.Count > 1)
				TrueSegment.RemoveEndingReturn();
			FalseSegment = new CodeSegment(falseStatements);
			if (FalseSegment.Statements.Count > 1)
				FalseSegment.RemoveEndingReturn();

			// determine the condition
			Condition = ConditionHelper.CombineConditions(conditions, TrueOffset);
		}

		public override string ToString()
		{
			string result = string.Format("if ({0})\n{{\n{1}}}\n", Condition, TrueSegment);
			if (FalseOffset != EndOffset)
				result += string.Format("\nelse\n{{\n{0}}}", FalseSegment);
			return result;
		}

		public override JSWriter ToJSWriter()
		{
			JSWriter writer = new JSWriter();
			writer.AddIfBlock(Condition, TrueSegment.ToJSWriter(), FalseSegment.ToJSWriter());
			return writer;
		}
	}
}
